package cn.szabadsag.hrm.service.impl;

import cn.szabadsag.hrm.config.RabbitMQConfig;
import cn.szabadsag.hrm.context.LoginContext;
import cn.szabadsag.hrm.domain.KillCourse;
import cn.szabadsag.hrm.domain.LoginUser;
import cn.szabadsag.hrm.dto.KillDto;
import cn.szabadsag.hrm.dto.KillOrderDto;
import cn.szabadsag.hrm.dto.OrderDelayMessage2MQDto;
import cn.szabadsag.hrm.exception.Assert;
import cn.szabadsag.hrm.exception.BusinessException;
import cn.szabadsag.hrm.mapper.KillCourseMapper;
import cn.szabadsag.hrm.service.IKillCourseService;
import cn.szabadsag.hrm.util.StrUtils;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.DateUtils;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yangcong
 * @since 2022-04-21
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 添加秒杀课程
     * @param killCourse：传递的实体
     */
    @Override
    public void add(KillCourse killCourse) {
        //  1.参数判断
        //  2.同一个活动中，不能出现重复商品
        //  3.保存秒杀到数据库
        killCourse.setKillSort(0);
        killCourse.setCreateTime(new Date());
        killCourse.setKillStatus(KillCourse.STATE_OFFLINE);
        insert(killCourse);

    }

    /**
     * 发布秒杀课程到Redis
     * 1.查询待发布，近两天需要秒杀的
     * 2.把秒杀课程的库存放入Redis，使用信号量
     * 3.状态修改为已上架，把秒杀课程放入Redis
     * 4.修改数据库，状态修改为已上架
     */
    @Override
    public void publisCourse2Redis() {
        //* 1.查询待发布，近两天需要秒杀的
        Wrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("kill_status",KillCourse.STATE_OFFLINE);
        //  近两天需要秒杀的
        Date now = new Date();
        Date endTime = DateUtils.addDays(now,2);
        wrapper.between("start_time",now,endTime);
        List<KillCourse> killCourses = selectList(wrapper);
        if (killCourses.isEmpty())return;
        for (KillCourse killCourse : killCourses) {

            RLock rLock = redissonClient.getLock("lock_kill_course:" + killCourse.getId());
            //  加锁
            rLock.lock();
            try{
                //  判断重复
                Boolean exist = redisTemplate.opsForHash().hasKey("KILL_COURSE", killCourse.getId().toString());
                if (exist){
                    log.warn("课程已经上架 {}",killCourse);
                    continue;
                }
                //* 2.把秒杀课程的库存放入Redis，使用信号量
                Integer killStore = killCourse.getKillStore();
                RSemaphore semaphore = redissonClient.getSemaphore("store:" + killCourse.getId());
                boolean setPermitsSuccess = semaphore.trySetPermits(killStore);
                if (setPermitsSuccess){
                    //* 3.状态修改为已上架，把秒杀课程放入Redis
                    killCourse.setKillStatus(KillCourse.STATE_ONLINE);
                    //  设置秒杀码
                    String code = StrUtils.getComplexRandomString(6);
                    killCourse.setKillCode(code);
                    redisTemplate.opsForHash().put("KILL_COURSE",killCourse.getId().toString(),killCourse);
                    //* 4.修改数据库，状态修改为已上架
                    updateById(killCourse);
                    log.info("课程成功发布到Redis {}",killCourse);
                }
            }finally {
                //  解锁
                log.info("解锁");
                if (rLock.isLocked()) rLock.unlock();
            }
        }
    }

    /**
     * 从Redis查询秒杀列表
     * @return
     */
    @Override
    public List<KillCourse> selectList4Online() {
        BoundHashOperations<Object, String, KillCourse> operations = redisTemplate.boundHashOps("KILL_COURSE");
        return operations.values();
    }

    /**
     * 从Redis查询单个秒杀
     * @return
     */
    @Override
    public KillCourse selectOne4Online(Long id) {
        Assert.assertNotNull(id,"非法请求");
        return (KillCourse) redisTemplate.opsForHash().get("KILL_COURSE",id.toString());
    }

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
    /**
     * 执行秒杀
     */
    @Override
    public String kill(KillDto killDto) {
        //  1.登录了吗？
//        LoginUser login = LoginContext.getLogin();
        LoginUser login = new LoginUser();//模拟数据
        login.setId(3L);//模拟登录
        Assert.assertNotNull(login,"请登录！");
        //  2.时间合法吗？
        Long killId = killDto.getId();
        KillCourse killCourse = (KillCourse) redisTemplate.opsForHash().get("KILL_COURSE", killId.toString());
        Assert.assertNotNull(killCourse,"非法请求！");
        Date now = new Date();
        if (now.before(killCourse.getStartTime()) || now.after(killCourse.getEndTime())){
            Assert.assertNotNull(killCourse,"秒杀已结束！");
        }
        //  3.秒杀码是否正确
        if (!killCourse.getKillCode().equals(killDto.getKillCode())){
            throw new BusinessException("非法请求！");
        }
        //  4.不能重复秒杀
        String killLog = "log:" + login.getId() + ":" + killId;
        if (redisTemplate.hasKey(killLog)){
            throw new BusinessException("机会只有一次！");
        }

        //  5.尝试扣减信号量
        RSemaphore semaphore = redissonClient.getSemaphore("store:" + killCourse.getId());
        boolean killSuccess = semaphore.tryAcquire(1);
        //  6.扣减失败
        Assert.assertTrue(killSuccess,"秒杀已结束！");
        //  7.扣减成功

        //  8.秒杀下单-构建订单数据库
        String timeStr = simpleDateFormat.format(now);
        String orderNo = timeStr + StrUtils.getComplexRandomString(6);
        //  预创订单
        KillOrderDto orderDto = new KillOrderDto(
                login.getId(),
                orderNo,
                killCourse.getKillPrice(),
                now,
                killCourse.getId(),
                killCourse.getCourseId(),
                killCourse.getCourseName(),
                killCourse.getCoursePic()
        );
        String key = "killorder:" + orderNo + ":" + login.getId();
        redisTemplate.opsForValue().set(key,orderDto);

        //  添加秒杀记录
        long expire = killCourse.getEndTime().getTime() - now.getTime();
        redisTemplate.opsForValue().set(killLog,1,expire, TimeUnit.MILLISECONDS);

        //  发送订单延迟消息到MQ
        rabbitTemplate.convertAndSend(
                //  交换机
                RabbitMQConfig.NAME_EXCHANGE_TOPIC_KILL,
                //  发送给哪个routingkey
                RabbitMQConfig.ROUTINGKEY_ORDER_DELAY,
                //  消息对象
                new OrderDelayMessage2MQDto(login.getId(), killId, orderNo)
        );

        //  9.返回订单号给用户
        return orderNo;
    }


}
